package org.dromara.solonplugins.job.impl;

import org.dromara.solonplugins.job.*;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Job执行器
 *
 * @author: aoshiguchen
 */
public class JobExecutor implements IJobExecutor {
	private IJobSource jobSource;
	private ThreadPoolExecutor threadPoolExecutor;
	private Map<String, JobInfo> jobInfoMap = new ConcurrentHashMap<>();
	private SchedulerFactory schedulerFactory;
	private Scheduler scheduler;
	private Map<String, IJobHandler> jobHandlerMap = new ConcurrentHashMap<>();
	private Map<String, String> jobInfoNameMap = new HashMap<>();
	private Set<String> runJobSet = new HashSet<>();
	private IJobCallback jobCallback;
	private Map<String, TriggerKey> triggerKeyMap = new ConcurrentHashMap<>();

	/**
	 * 执行器启动
	 */
	public void start() {
		if (null == threadPoolExecutor) {
			// 用户如果没有自定义线程池，则使用默认的
			threadPoolExecutor = new ThreadPoolExecutor(5, 20, 10L, TimeUnit.SECONDS,
					new LinkedBlockingQueue<>(), new CustomThreadFactory("SolonJob"));
		}

		try {
			this.schedulerFactory = new StdSchedulerFactory();
			this.scheduler = schedulerFactory.getScheduler();
			this.init();
		} catch (Exception e){
			throw new RuntimeException("job初始化异常");
		}
	}

	/**
	 * 设置Job源
	 *
	 * @param jobSource Job来源
	 */
	public void setJobSource(IJobSource jobSource) {
		this.jobSource = jobSource;
	}

	/**
	 * 设置线程池
	 *
	 * @param threadPoolExecutor 线程池
	 */
	public void setThreadPoolExecutor(ThreadPoolExecutor threadPoolExecutor) {
		this.threadPoolExecutor = threadPoolExecutor;
	}

	/**
	 * 设置Job回调
	 *
	 * @param jobCallback job回调
	 */
	public void setJobCallback(IJobCallback jobCallback) {
		this.jobCallback = jobCallback;
	}

	/**
	 * 初始化
	 * @throws Exception
	 */
	@Override
	public void init() throws Exception {
		List<JobInfo> jobInfoList = jobSource.sourceList();
		if (null == jobInfoList || jobInfoList.isEmpty()) {
			return;
		}

		for (JobInfo jobInfo : jobInfoList) {
			add(jobInfo);
		}
	}

	/**
	 * 新增job
	 * @param jobInfo job信息
	 */
	@Override
	public void add(JobInfo jobInfo) {
		if (null == jobInfo ||
			null == jobInfo.getName() || jobInfo.getName().length() == 0 ||
			null == jobInfo.getCron() || jobInfo.getCron().length() == 0 ||
				null == jobInfo.getJobHandler()
		) {
			return;
		}
		synchronized (jobInfo.getName()) {
			jobInfoNameMap.put(jobInfo.getId(), jobInfo.getName());
			jobHandlerMap.put(jobInfo.getName(), jobInfo.getJobHandler());
			jobInfoMap.put(jobInfo.getName(), jobInfo);
			if (jobInfo.isEnable()) {
				// 启动job
				start(jobInfo.getName());
			}
		}
	}

	/**
	 * 删除job
	 * @param jobName Job名称
	 */
	@Override
	public void remove(String jobName) {
		JobInfo jobInfo = jobInfoMap.get(jobName);
		if (null == jobInfo) {
			return;
		}
		synchronized (jobName) {
			jobInfoMap.remove(jobName);
			jobHandlerMap.remove(jobInfo.getName());
			runJobSet.remove(jobInfo.getName());
			unscheduleJob(jobName);
		}
	}

	@Override
	public void removeById(String jobId) {
		String jobName = jobInfoNameMap.get(jobId);
		remove(jobName);
	}

	/**
	 * 启动job调度
	 * @param jobName job名称
	 */
	@Override
	public void start(String jobName) {
		JobInfo jobInfo = jobInfoMap.get(jobName);
		if (null == jobInfo) {
			return;
		}
		synchronized (jobName) {
			runJobSet.add(jobInfo.getName());
			TriggerKey triggerKey = TriggerKey.triggerKey(jobInfo.getName());
			triggerKeyMap.put(jobInfo.getName(), triggerKey);
			JobKey jobKey = new JobKey(jobInfo.getName());

			CronScheduleBuilder cronScheduleBuilder = CronScheduleBuilder.cronSchedule(jobInfo.getCron()).withMisfireHandlingInstructionDoNothing();
			CronTrigger cronTrigger = TriggerBuilder.newTrigger().withIdentity(triggerKey).withSchedule(cronScheduleBuilder).build();
			JobDetail jobDetail = JobBuilder.newJob(JobBean.class).withIdentity(jobKey).build();

			try {
				scheduler.scheduleJob(jobDetail, cronTrigger);
				scheduler.start();
			} catch (Exception e) {
				throw new RuntimeException(String.format("新增job[id=%s]异常", jobInfo.getName()));
			}
		}
	}

	@Override
	public void startById(String jobId) {
		String jobName = jobInfoNameMap.get(jobId);
		start(jobName);
	}

	/**
	 * 停止job调度
	 * @param jobName job名称
	 */
	@Override
	public void stop(String jobName) {
		JobInfo jobInfo = jobInfoMap.get(jobName);
		if (null == jobInfo) {
			return;
		}
		synchronized (jobName) {
			runJobSet.remove(jobName);
			unscheduleJob(jobName);
		}
	}

	@Override
	public void stopById(String jobId) {
		String jobName = jobInfoNameMap.get(jobId);
		stop(jobName);
	}

	/**
	 * 触发
	 * @param jobName job名称
	 * @param param job参数
	 */
	@Override
	public void trigger(String jobName, String param) {
		doExecute(jobName, param);
	}

	@Override
	public void triggerById(String jobId, String param) {
		String jobName = jobInfoNameMap.get(jobId);
		trigger(jobName, param);
	}

	/**
	 * 执行
	 *
	 * @param context 上下文
	 * @throws JobExecutionException job执行异常
	 */
	public void execute(JobExecutionContext context) throws JobExecutionException {
		if (null == context || null == context.getTrigger()) {
			return;
		}
		String jobName = context.getTrigger().getKey().getName();
		JobInfo jobInfo = jobInfoMap.get(jobName);
		if (null == jobInfo) {
			unscheduleJob(jobName);
			return;
		}
		doExecute(jobName, jobInfo.getParam());
	}

	/**
	 * 取消job调度
	 * @param jobName job名称
	 */
	private void unscheduleJob(String jobName) {
		TriggerKey triggerKey = triggerKeyMap.get(jobName);
		if (null != triggerKey) {
			try {
				scheduler.unscheduleJob(triggerKey);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 执行job
	 * @param jobName job名称
	 * @param param job参数
	 */
	private void doExecute(String jobName, String param) {
		JobInfo jobInfo = jobInfoMap.get(jobName);
		if (null == jobInfo) {
			return;
		}
		IJobHandler jobHandler = jobHandlerMap.get(jobInfo.getName());
		if (null == jobHandler) {
			return;
		}
		threadPoolExecutor.submit(() -> {
			try {
				jobHandler.execute(param);
				if (null != jobCallback) {
					jobCallback.executeLog(jobInfo, param, null);
				}
			} catch (Throwable e) {
				jobCallback.executeLog(jobInfo, param, e);
			}
		});
	}
}
